<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Database\QueryException;
use Input;
use DB;
use Log;
use App\WechatMsg;
use App\TicketRank;
use App\State;
use App\Http\Requests;
use App\Http\Controllers\Controller;

class WechatController extends Controller
{
    private $config;

    private function ReadConfig()
    {
        $this->config = DB::table('config')->first();
    }

    private function GenerateVerifyCode()
    {
        $letters = explode(',', $this->config->verifycode_letters);
        $length = intval($this->config->verifycode_length);
        $result = '';
        for ($i = 0; $i < $length; ++$i)
            $result .= $letters[array_rand($letters)];
        return $result;
    }

    private function CheckSignature()
    {
        $signature = Input::get('signature');
        $timestamp = Input::get('timestamp');
        $nonce = Input::get('nonce');
        $token = env('WECHAT_TOKEN', 'no token found');
        $tmp_arr = array($token, $timestamp, $nonce);
        sort($tmp_arr, SORT_STRING);
        $result = sha1(implode($tmp_arr));
        return strval($result) === strval($signature);
    }

    private function CheckMessage($message)
    {
        if ($message->MsgType == 'event') {
            if (WechatMsg::where('MsgType', '=', 'event')->where('FromUserName', '=', $message->FromUserName)->where('CreateTime', '=', $message->CreateTime)->count() != 0)
                return 'duplicate';

            if ($message->Event == 'subscribe')
                return 'subscribe';

            return 'unsubscribe';
        }
        else {
            if (WechatMsg::where('MsgId', '=', $message->MsgId)->count() != 0)
                return 'duplicate';

            if ($message->MsgType != 'text')
                return 'other';

            $allowed_msgs = json_decode($this->config->allowed_msgs, true);
            if (!in_array(strtolower($message->Content), $allowed_msgs))
                return 'other';

            if (TicketRank::where('FromUserName', '=', $message->FromUserName)->count() != 0)
                return 'repeat';

            return 'valid';
        }
    }

    private function StoreMsg($message)
    {
        $wechatmsg = new WechatMsg;

        $wechatmsg->ToUserName = $message->ToUserName;
        $wechatmsg->FromUserName = $message->FromUserName;
        $wechatmsg->CreateTime = $message->CreateTime;
        $wechatmsg->MsgType = $message->MsgType;

        if ($message->MsgType == 'text') {
            $wechatmsg->Content = $message->Content;
            $wechatmsg->MsgId = $message->MsgId;
        }
        else if ($message->MsgType == 'event') {
            $wechatmsg->Event = $message->Event;
        }

        $wechatmsg->save();
    }

    private function RecordValidMsg($message, $verifycode)
    {
        $ticketrank = new TicketRank;

        // Notice: there is UNIQUE attribute on `FromUserName` field.
        $ticketrank->FromUserName = $message->FromUserName;
        $ticketrank->Content = $message->Content;
        $ticketrank->MsgId = $message->MsgId;
        $ticketrank->verifycode = $verifycode;

        $ticketrank->save();

        return intval($ticketrank->id);
    }

    private function Reply($old_message, $reply_content)
    {
        if ($old_message->MsgType == 'event') {
            $wechatmsg = WechatMsg::where('MsgType', '=', 'event')->where('FromUserName', '=', $old_message->FromUserName)->where('CreateTime', '=', $old_message->CreateTime)->first();
            $wechatmsg->reply = $reply_content;
            $wechatmsg->save();
        }
        else {
            $wechatmsg = WechatMsg::where('MsgId', '=', $old_message->MsgId)->first();
            $wechatmsg->reply = $reply_content;
            $wechatmsg->save();
        }

        $wechat_reply = [
            'ToUserName'   => $old_message->FromUserName,
            'FromUserName' => $old_message->ToUserName,
            'CreateTime'   => time(),
            'MsgType'      => 'text',
            'Content'      => $reply_content
        ];

        return view('reply', $wechat_reply);
    }

    public function FirstVerify()
    {
        if (!$this->CheckSignature())
            return 'Invalid Request';

        $echostr = Input::get('echostr');
        return $echostr;
    }

    public function MessageHandler()
    {
        if (!$this->CheckSignature())
            return 'Invalid Request';

        $this->ReadConfig();

        $raw_message = file_get_contents('php://input');
        $message = simplexml_load_string($raw_message, 'SimpleXMLElement', LIBXML_NOCDATA);
        $message->Content = trim($message->Content);

        $check_result = $this->CheckMessage($message);
        if ($check_result == 'valid') {
            $vie_start_time = strtotime($this->config->vie_start_time);
            if (time() < $vie_start_time) {
                $this->StoreMsg($message);
                return $this->Reply($message, $this->config->early_reply);
            }

            $this->StoreMsg($message);

            $ticket_num = intval($this->config->ticket_num);

            // The following code needs further inspection.
            // Bug of delivering not enough number of tickets may still appear.
            // Transcation does not seem to work well. 

            $pdo = DB::connection()->getPdo();
            $pdo->exec('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
            DB::beginTransaction();

            try {
                $state = DB::table('state')->where('id', 1)->sharedLock()->get();
                $remain_ticket = intval($state[0]->ticket_remain);
                if ($remain_ticket <= 0) {
                    DB::commit();
                    return $this->Reply($message, $this->config->slow_reply);
                }

                $verifycode = $this->GenerateVerifyCode();
                while (TicketRank::where('verifycode', '=', $verifycode)->count() != 0)
                    $verifycode = $this->GenerateVerifyCode();

                $record_id = $this->RecordValidMsg($message, $verifycode);

                // There may be a race condition?
                DB::table('state')->where('id', 1)->lockForUpdate()->decrement('ticket_remain', 1);
                $state = State::first();
                $remain_ticket = $state->ticket_remain;
                DB::commit();

                if ($remain_ticket < 0)
                    return $this->Reply($message, $this->config->slow_reply);

                $success_reply = str_replace('{{verifycode}}', $verifycode, $this->config->success_reply);
                $success_reply = str_replace('{{rank}}', $ticket_num - $remain_ticket, $success_reply);

                return $this->Reply($message, $success_reply);
            } catch (\Exception $e) {
                DB::rollback();
                Log::error($e);
                return $this->Reply($message, $this->config->fail_reply);
            }

        }
        else if ($check_result == 'repeat') {
            $this->StoreMsg($message);
            return $this->Reply($message, $this->config->repeat_reply);
        }
        else if ($check_result == 'other') {
            $this->StoreMsg($message);
            return $this->Reply($message, $this->config->other_reply);
        }
        else if ($check_result == 'subscribe') {
            $this->StoreMsg($message);
            return $this->Reply($message, $this->config->subscribe_reply);
        }
        else if ($check_result == 'unsubscribe') {
            $this->StoreMsg($message);
            return 'success';
        }
        else {
            return 'success';
        }
    }
}
